#include "SvnClient.h"

// Memory access errors: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3088542&SiteID=1
// enable unmanaged code debugging: http://msdn2.microsoft.com/en-us/library/tdw0c6sf(VS.80).aspx
IronSvn::SvnClient::SvnClient()
{
	this->Initialize(nullptr, nullptr);
}

IronSvn::SvnClient::SvnClient(String^ userName, String^ password)
{
	this->Initialize(userName, password);
}

void IronSvn::SvnClient::Initialize(String^ userName, String^ password)
{
	// Do we need apr_app_initialize?
    //apr_app_initialize(&argc, &argv, NULL);
	this->m_thisWrapper = gcnew IronSvn::SvnClientWrapper(this);
	this->m_wrapper = new gcroot<SvnClientWrapper^>(this->m_thisWrapper);
	// create the allocator
	apr_allocator_t* allocator;
	apr_status_t status = apr_allocator_create(&allocator);
	if (status)
	{
		throw gcnew Exception("Fatal: Error creating APR allocator: " + status);
	}

	status = apr_pool_initialize();
	if (status)
	{
		throw gcnew Exception("Fatal: Error initializing APR pool: " + status);
	}

	// create the pool
	apr_pool_t* pool = svn_pool_create_ex(NULL, allocator);

	if (pool == NULL)
	{
		throw gcnew Exception("Fatal: Could not create SVN pool");
	}

	this->m_pool = pool;

	// create the context
	svn_client_ctx_t* ctx;
	svn_auth_baton_t *ab;
	IronSvn::Svn::ThrowIfError(svn_client_create_context(&ctx, this->m_pool));
	memset((void *)ctx, 0, sizeof(svn_client_ctx_t));

	ctx->log_msg_baton = NULL;
	ctx->log_msg_func2 = &GetLogMessage;
	ctx->notify_func2 = &Notify;
	ctx->notify_baton = NULL;
	ctx->notify_baton2 = NULL;
	
	apr_array_header_t *providers = apr_array_make(this->m_pool, 11, sizeof(svn_auth_provider_object_t*));
    svn_auth_provider_object_t *provider;

	// Add the default auth providers which don't require explicit authentication (e.g. cached credentials)
    svn_client_get_windows_simple_provider(&provider, pool);
    APR_ARRAY_PUSH (providers, svn_auth_provider_object_t*) = provider;
    svn_client_get_simple_provider(&provider, pool);
    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t*) = provider;
    svn_client_get_username_provider(&provider, pool);
    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t*) = provider;
    svn_client_get_ssl_server_trust_file_provider(&provider, pool);
    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t*) = provider;
    svn_client_get_ssl_client_cert_file_provider(&provider, pool);
    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t*) = provider;
    svn_client_get_ssl_client_cert_pw_file_provider(&provider, pool);
    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t*) = provider;

	svn_client_get_simple_prompt_provider(
		&provider,
		&AuthUserPassword,
		(void *)this->m_wrapper,
		1,
		this->m_pool
	);
	APR_ARRAY_PUSH(providers, svn_auth_provider_object_t*) = provider;

	svn_client_get_username_prompt_provider(
		&provider,
        &AuthUser,
		(void *)this->m_wrapper,
        1,
        this->m_pool
	);
	APR_ARRAY_PUSH(providers, svn_auth_provider_object_t*) = provider;

	svn_client_get_ssl_server_trust_prompt_provider(
		&provider,
		&AuthSslTrust,
		(void *)this->m_wrapper,
		pool
	);
	APR_ARRAY_PUSH(providers, svn_auth_provider_object_t*) = provider;

	svn_client_get_ssl_client_cert_prompt_provider(
		&provider,
		&AuthSsl,
		(void *)this->m_wrapper,
		1,
		pool
	);
	APR_ARRAY_PUSH(providers, svn_auth_provider_object_t*) = provider;

	svn_client_get_ssl_client_cert_pw_prompt_provider(
		&provider,
		&AuthSslPassword,
		(void *)this->m_wrapper,
		1,
		pool
	);
	APR_ARRAY_PUSH(providers, svn_auth_provider_object_t*) = provider;

    svn_auth_open(&ab, providers, pool);
	ctx->auth_baton = ab;

	this->m_ctx = ctx;
	
	svn_fs_initialize(this->m_pool);

	this->UserName = userName;
	this->Password = password;
}

IronSvn::SvnClient::~SvnClient()
{
	if (this->m_userChars)
	{
		Marshal::FreeHGlobal(IntPtr((void*)this->m_userChars));
		this->m_userChars = NULL;
	}
	if (this->m_passwordChars)
	{
		Marshal::FreeHGlobal(IntPtr((void*)this->m_passwordChars));
		this->m_passwordChars = NULL;
	}
	if (this->m_wrapper)
	{
		delete this->m_wrapper;
	}
	if (this->m_pool)
	{
		svn_pool_destroy(this->m_pool);
	}
}

IronSvn::SvnClientWrapper::SvnClientWrapper(IronSvn::SvnClient^ client)
{
	this->m_client = client;
}

IronSvn::SvnClientWrapper::~SvnClientWrapper()
{
}

IronSvn::SvnClient^ IronSvn::SvnClientWrapper::Client::get()
{
	return this->m_client;
}

void Notify(
	void *baton,
	const svn_wc_notify_t *notify,
	apr_pool_t *pool
)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine("> Notify");
#endif

	if (baton)
	{
		gcroot<List<IronSvn::SvnAction^>^>* list = (gcroot<List<IronSvn::SvnAction^>^>*)baton;
		IronSvn::SvnAction^ action = gcnew IronSvn::SvnAction();
		(*list)->Add(action);

		action->Path = gcnew String(notify->path);
		action->Action = (IronSvn::SvnActionType)((int)notify->action);
		action->Kind = (IronSvn::SvnNodeKind)((int)notify->kind);
		action->ContentState = (IronSvn::SvnActionState)((int)notify->content_state);
		action->PropertyState = (IronSvn::SvnActionState)((int)notify->prop_state);
		action->Revision = (long)notify->revision;
		if (notify->mime_type)
		{
			action->MimeType = gcnew String(notify->mime_type);
		}
		if (notify->err)
		{
			action->Error = IronSvn::Svn::GetError(notify->err);
		}
		action->LockState = (IronSvn::SvnLockState)((int)notify->lock_state);

#ifdef LOGS
		if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("Action={0}", action->ToString()));
#endif
	}
	
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine("< Notify");
#endif
}

svn_error_t* AuthUserPassword(
	svn_auth_cred_simple_t **cred_p, 
	void *baton,
	const char *realm,
	const char *username, 
	svn_boolean_t may_save,
	apr_pool_t *pool
)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine("> AuthUserPassword");
#endif

	gcroot<IronSvn::SvnClientWrapper^>* wrapper = (gcroot<IronSvn::SvnClientWrapper^>*)baton;
	svn_auth_cred_simple_t *ret = (svn_auth_cred_simple_t*)apr_pcalloc(pool, sizeof(*ret));
	ret->username = (*wrapper)->Client->UserNameChars;
	ret->password = (*wrapper)->Client->PasswordChars;
	ret->may_save = true;
	*cred_p = ret;

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine("< AuthUserPassword");
#endif

	return SVN_NO_ERROR;
}

svn_error_t* AuthUser(
	svn_auth_cred_username_t **cred_p, 
	void *baton,
	const char *realm, 
	svn_boolean_t may_save, 
	apr_pool_t *pool
)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine("> AuthUser");
#endif

	gcroot<IronSvn::SvnClientWrapper^>* wrapper = (gcroot<IronSvn::SvnClientWrapper^>*)baton;
	svn_auth_cred_username_t *ret = (svn_auth_cred_username_t*)apr_pcalloc(pool, sizeof(*ret));
	ret->username = (*wrapper)->Client->UserNameChars;
	ret->may_save = true;
	*cred_p = ret;

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine("< AuthUser");
#endif

	return SVN_NO_ERROR;
}

svn_error_t* AuthSslTrust(
	svn_auth_cred_ssl_server_trust_t **cred_p,
	void *baton,
	const char *realm,
	apr_uint32_t failures,
	const svn_auth_ssl_server_cert_info_t *cert_info,
	svn_boolean_t may_save,
	apr_pool_t *pool
)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine("> AuthSslTrust");
#endif

	gcroot<IronSvn::SvnClientWrapper^>* wrapper = (gcroot<IronSvn::SvnClientWrapper^>*)baton;
	svn_auth_cred_ssl_server_trust_t *ret = (svn_auth_cred_ssl_server_trust_t*)apr_pcalloc(pool, sizeof(*ret));
	ret->accepted_failures = failures;
	ret->may_save = true;
	*cred_p = ret;

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine("< AuthSslTrust");
#endif

	return SVN_NO_ERROR;
}

svn_error_t* AuthSsl(
	svn_auth_cred_ssl_client_cert_t **cred_p,
	void *baton, 
	const char *realm, 
	svn_boolean_t may_save,
	apr_pool_t *pool
)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine("> AuthSsl");
#endif

	gcroot<IronSvn::SvnClientWrapper^>* wrapper = (gcroot<IronSvn::SvnClientWrapper^>*)baton;
	svn_auth_cred_ssl_client_cert_t *ret = (svn_auth_cred_ssl_client_cert_t*)apr_pcalloc(pool, sizeof(*ret));
	//ret->cert_file = NULL;
	//ret->may_save = false;
	//*cred_p = ret;
	//return SVN_NO_ERROR;

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine("< AuthSsl");
#endif

	return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, "User canceled");
}

svn_error_t* AuthSslPassword(
	svn_auth_cred_ssl_client_cert_pw_t **cred_p,
	void *baton, 
	const char *realm, 
	svn_boolean_t may_save,
	apr_pool_t *pool
)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine("> AuthSslPassword");
#endif

	gcroot<IronSvn::SvnClientWrapper^>* wrapper = (gcroot<IronSvn::SvnClientWrapper^>*)baton;
	svn_auth_cred_ssl_client_cert_pw_t *ret = (svn_auth_cred_ssl_client_cert_pw_t*)apr_pcalloc(pool, sizeof(*ret));
	ret->password = (*wrapper)->Client->PasswordChars;
	ret->may_save = true;
	*cred_p = ret;

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine("< AuthSslPassword");
#endif

	return SVN_NO_ERROR;
}

bool IronSvn::SvnClient::HasTarget(String^ path)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> HasTarget {0}", path));
#endif

	path = IronSvn::Svn::PreparePath(path);
	apr_hash_t* dirents;

	svn_opt_revision_t revision;
	revision.kind = svn_opt_revision_head;

	const char* cpath = (const char*)(Marshal::StringToHGlobalAnsi(path)).ToPointer();
	bool result = false;

	try
	{
		svn_error_t* err = svn_client_ls(
			&dirents,
			cpath,
			&revision,
			true,
			this->m_ctx,
			this->m_pool
		);
		if (err == NULL)
		{
			result = true;
		}
	}
	finally
	{
		Marshal::FreeHGlobal(IntPtr((void*)cpath));
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< HasTarget {0}", result));
#endif

	return result;
}

void IronSvn::SvnClient::Cleanup(String^ workingCopy)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> Cleanup {0}", workingCopy));
#endif

	const char* cworkingCopy = (const char*)(Marshal::StringToHGlobalAnsi(workingCopy)).ToPointer();
	try
	{
		IronSvn::Svn::ThrowIfError(svn_client_cleanup(
			cworkingCopy,
			this->m_ctx,
			this->m_pool
		));
	}
	finally
	{
		Marshal::FreeHGlobal(IntPtr((void*)cworkingCopy));
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< Cleanup"));
#endif
}

void IronSvn::SvnClient::Revert(String^ path)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> Revert {0}", path));
#endif

	const char* cpath = (const char*)(Marshal::StringToHGlobalAnsi(path)).ToPointer();
	try
	{
		apr_array_header_t* targets = IronSvn::Svn::GetStringAsAprArray(cpath, this->m_pool);
		IronSvn::Svn::ThrowIfError(svn_client_revert(
			targets,
			true,
			this->m_ctx,
			this->m_pool
		));
	}
	finally
	{
		Marshal::FreeHGlobal(IntPtr((void*)cpath));
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< Revert"));
#endif
}

IronSvn::SvnResult^ IronSvn::SvnClient::Checkout(String^ url, String^ checkoutPath)
{
	return this->Checkout(url, checkoutPath, -1);
}

IronSvn::SvnResult^ IronSvn::SvnClient::Checkout(String^ url, String^ checkoutPath, long rev)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> Checkout {0}, {1}, {2}", url, checkoutPath, rev));
#endif

	IronSvn::SvnResult^ result = gcnew IronSvn::SvnResult();
	checkoutPath = IronSvn::Svn::PreparePath(checkoutPath);

	// see C:\projects\IronSvn\rebuild\subversion-1.4.6\subversion\bindings\java\javahl\native\SVNClient.cpp
	svn_revnum_t resultrev;
	svn_opt_revision_t peg_revision;
	svn_opt_revision_t revision;

	// See for peg_revision: http://svnbook.red-bean.com/nightly/en/svn.advanced.pegrevs.html
	peg_revision.kind = svn_opt_revision_unspecified;

	if (rev < 0)
	{
		revision.kind = svn_opt_revision_head;
	}
	else
	{
		revision.kind = svn_opt_revision_number;
		revision.value.number = rev;
	}

	// for converting strings, see http://msdn2.microsoft.com/en-us/magazine/cc301733.aspx
	const char* curl = (const char*)(Marshal::StringToHGlobalAnsi(url)).ToPointer();

	try
	{
		const char* ccheckoutPath = (const char*)(Marshal::StringToHGlobalAnsi(checkoutPath)).ToPointer();
		gcroot<List<IronSvn::SvnAction^>^>* gclist = NULL;
		try
		{
			gclist = new gcroot<List<IronSvn::SvnAction^>^>(result->Actions);
			this->m_ctx->notify_baton2 = (void *)gclist;
			IronSvn::Svn::ThrowIfError(svn_client_checkout2(
				&resultrev,
				curl,
				ccheckoutPath,
				&peg_revision,
				&revision,
				true,
				false,
				this->m_ctx,
				this->m_pool
			));
		}
		finally
		{
			this->m_ctx->notify_baton2 = NULL;
			if (gclist)
			{
				delete gclist;
			}
			Marshal::FreeHGlobal(IntPtr((void*)ccheckoutPath));
		}
	}
	finally
	{
		Marshal::FreeHGlobal(IntPtr((void*)curl));
	}

	result->Result = resultrev;

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< Checkout {0}", result));
#endif

	return result;
}

List<IronSvn::SvnNode^>^ IronSvn::SvnClient::Changed(String^ repo)
{
	return Changed(repo, gcnew SvnRevOrTxn());
}

List<IronSvn::SvnNode^>^ IronSvn::SvnClient::Changed(String^ repo, long revision)
{
	return Changed(repo, gcnew SvnRevOrTxn(revision));
}

List<IronSvn::SvnNode^>^ IronSvn::SvnClient::Changed(String^ repo, String^ transaction)
{
	return Changed(repo, gcnew SvnRevOrTxn(transaction));
}

List<IronSvn::SvnNode^>^ IronSvn::SvnClient::Changed(String^ repo, SvnRevOrTxn^ target)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> Changed {0}, {1}", repo, target));
#endif

	List<IronSvn::SvnNode^>^ result = gcnew List<IronSvn::SvnNode^>();
	if (target == nullptr)
	{
		throw gcnew ArgumentNullException("target");
	}

	repo = IronSvn::Svn::PreparePath(repo);

	if (repo->StartsWith("file:///"))
	{
		repo = repo->Substring(8);
	}

	const char* crepo = (const char*)(Marshal::StringToHGlobalAnsi(repo)).ToPointer();
	try
	{
		svn_repos_t* repos;
		IronSvn::Svn::ThrowIfError(svn_repos_open(&repos, crepo, this->m_pool));

		svn_fs_t* fs = svn_repos_fs(repos);

		svn_revnum_t base_rev;
		svn_fs_txn_t* txn = NULL;
		svn_fs_root_t* root;
		
		if (target->IsTransaction())
		{
			const char* ctxn = (const char*)(Marshal::StringToHGlobalAnsi(target->Transaction)).ToPointer();
			try
			{
				svn_fs_open_txn(&(txn), fs, ctxn, this->m_pool);
				base_rev = svn_fs_txn_base_revision(txn);
				IronSvn::Svn::ThrowIfError(svn_fs_txn_root(&root, txn, this->m_pool));
			}
			finally
			{
				Marshal::FreeHGlobal(IntPtr((void*)ctxn));
			}
		}
		else
		{
			svn_revnum_t rev;

			if (!SVN_IS_VALID_REVNUM(target->Revision) ||
				(target->Revision == 0 &&
				!target->IsExplicit))
			{
				IronSvn::Svn::ThrowIfError(svn_fs_youngest_rev(&rev, fs, this->m_pool));
			}
			else
			{
				rev = (svn_revnum_t)target->Revision;
			}

			IronSvn::Svn::ThrowIfError(svn_fs_revision_root(&root, fs, rev, this->m_pool));
			base_rev = rev - 1;
		}

		if (!SVN_IS_VALID_REVNUM(base_rev))
		{
			throw gcnew IronSvn::SvnException("Transaction " + target->ToString() + " not based on a revision");
		}

		svn_fs_root_t* base_root;
		IronSvn::Svn::ThrowIfError(svn_fs_revision_root(&base_root, fs, base_rev, this->m_pool));

		const svn_delta_editor_t *editor;
		void *edit_baton;

		apr_pool_t *edit_pool = svn_pool_create(this->m_pool);
		try
		{
			IronSvn::Svn::ThrowIfError(
				svn_repos_node_editor(
					&editor,
					&edit_baton,
					repos,
					base_root,
					root,
					edit_pool,
					this->m_pool
				)
			);

			IronSvn::Svn::ThrowIfError(
				svn_repos_replay2(
					root,
					"",
					SVN_INVALID_REVNUM,
					FALSE,
					editor,
					edit_baton,
					NULL,
					NULL,
					edit_pool
				)
			);

			svn_repos_node_t* tree = svn_repos_node_from_baton(edit_baton);
			ProcessTree(tree, repo, String::Empty, result);
		}
		finally
		{
			svn_pool_destroy(edit_pool);
		}
	}
	finally
	{
		Marshal::FreeHGlobal(IntPtr((void*)crepo));
	}
	
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< Changed {0}", result));
#endif
	
	return result;
}

void IronSvn::SvnClient::ProcessTree(svn_repos_node_t *node, String^ path, String^ relativePath, List<IronSvn::SvnNode^>^ result)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> ProcessTree {0}, {1}", path, relativePath));
#endif

	if (node)
	{
		IronSvn::SvnNode^ n = gcnew IronSvn::SvnNode();
		result->Add(n);

		n->Name = relativePath + gcnew String(node->name);
		switch (node->action)
		{
		case 'A':
			n->Action = IronSvn::SvnNodeAction::Add;
			break;
		case 'D':
			n->Action = IronSvn::SvnNodeAction::Delete;
			break;
		case 'R':
			n->Action = IronSvn::SvnNodeAction::Update;
			break;
		default:
			throw gcnew NotImplementedException("Unknown action " + node->action);
		}
		if (node->copyfrom_path)
		{
			n->CopyFromPath = gcnew String(node->copyfrom_path);
		}
		n->CopyFromRevision = (long)node->copyfrom_rev;
		n->Kind = (IronSvn::SvnNodeKind)node->kind;
		n->PropertyModifications = node->prop_mod ? true : false;
		n->TextualModifications = node->text_mod ? true : false;

		if (n->Kind == IronSvn::SvnNodeKind::Directory && !String::IsNullOrEmpty(n->Name))
		{
			relativePath = n->Name + "/";
		}

#ifdef LOGS
		if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("n={0}", n));
#endif

		node = node->child;
		if (node)
		{
			apr_pool_t* subpool = svn_pool_create(this->m_pool);
			try
			{
				String^ fullpath = IronSvn::Svn::SvnPathJoin(path, gcnew String(node->name));
				ProcessTree(node, fullpath, relativePath, result);
				while (node->sibling)
				{
					svn_pool_clear(subpool);
					node = node->sibling;
					fullpath = IronSvn::Svn::SvnPathJoin(path, gcnew String(node->name));
					ProcessTree(node, fullpath, relativePath, result);
				}
			}
			finally
			{
				svn_pool_destroy(subpool);
			}
		}
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< ProcessTree"));
#endif
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetAuthor(String^ repo)
{
	return GetAuthor(repo, gcnew SvnRevOrTxn());
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetAuthor(String^ repo, long revision)
{
	return GetAuthor(repo, gcnew SvnRevOrTxn(revision));
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetAuthor(String^ repo, String^ transaction)
{
	return GetAuthor(repo, gcnew SvnRevOrTxn(transaction));
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetAuthor(String^ repo, SvnRevOrTxn^ target)
{
	SvnLookInfo^ result = nullptr;
	long revision;

	const char* val = GetProperty(repo, target, SVN_PROP_REVISION_AUTHOR, &revision);

	if (val)
	{
		result = gcnew SvnLookInfo();
		result->Author = gcnew String(val);
		result->Revision = revision;
	}
	
	return result;
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetLog(String^ repo)
{
	return GetLog(repo, gcnew SvnRevOrTxn());
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetLog(String^ repo, long revision)
{
	return GetLog(repo, gcnew SvnRevOrTxn(revision));
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetLog(String^ repo, String^ transaction)
{
	return GetLog(repo, gcnew SvnRevOrTxn(transaction));
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetLog(String^ repo, SvnRevOrTxn^ target)
{
	IronSvn::SvnLookInfo^ result = nullptr;
	long revision;

	const char* val = GetProperty(repo, target, SVN_PROP_REVISION_LOG, &revision);

	if (val)
	{
		result = gcnew SvnLookInfo();
		result->Log = gcnew String(val);
		result->Revision = revision;
	}
	
	return result;
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetDate(String^ repo)
{
	return GetDate(repo, gcnew SvnRevOrTxn());
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetDate(String^ repo, long revision)
{
	return GetDate(repo, gcnew SvnRevOrTxn(revision));
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetDate(String^ repo, String^ transaction)
{
	return GetDate(repo, gcnew SvnRevOrTxn(transaction));
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetDate(String^ repo, SvnRevOrTxn^ target)
{
	IronSvn::SvnLookInfo^ result = nullptr;
	long revision;

	const char* val = GetProperty(repo, target, SVN_PROP_REVISION_DATE, &revision);

	if (val)
	{
		apr_time_t t;
		IronSvn::Svn::ThrowIfError(svn_time_from_cstring(&t, val, this->m_pool));
		result = gcnew SvnLookInfo();
		result->Date = IronSvn::Svn::ConvertAprTimeToDateTime(t);
		result->Revision = revision;
	}
	
	return result;
}

const char* IronSvn::SvnClient::GetProperty(String^ repo, SvnRevOrTxn^ target, const char* prop, long* revision)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> GetProperty {0}, {1}", repo, target));
#endif

	const char* result = NULL;

	if (revision)
	{
		*revision = 0;
	}

	if (target == nullptr)
	{
		throw gcnew ArgumentNullException("target");
	}

	repo = IronSvn::Svn::PreparePath(repo);

	if (repo->StartsWith("file:///"))
	{
		repo = repo->Substring(8);
	}

	const char* crepo = (const char*)(Marshal::StringToHGlobalAnsi(repo)).ToPointer();
	try
	{
		svn_repos_t* repos;
		IronSvn::Svn::ThrowIfError(svn_repos_open(&repos, crepo, this->m_pool));

		svn_fs_t* fs = svn_repos_fs(repos);

		svn_string_t* val;
		
		if (target->IsTransaction())
		{
			const char* ctxn = (const char*)(Marshal::StringToHGlobalAnsi(target->Transaction)).ToPointer();
			try
			{
				svn_fs_txn_t* txn = NULL;
				svn_fs_open_txn(&(txn), fs, ctxn, this->m_pool);
				IronSvn::Svn::ThrowIfError(svn_fs_txn_prop(&val, txn, prop, this->m_pool));
			}
			finally
			{
				Marshal::FreeHGlobal(IntPtr((void*)ctxn));
			}
		}
		else
		{
			svn_revnum_t rev;

			if (!SVN_IS_VALID_REVNUM(target->Revision) ||
				(target->Revision == 0 &&
				!target->IsExplicit))
			{
				IronSvn::Svn::ThrowIfError(svn_fs_youngest_rev(&rev, fs, this->m_pool));
			}
			else
			{
				rev = (svn_revnum_t)target->Revision;
			}

			IronSvn::Svn::ThrowIfError(svn_fs_revision_prop(&val, fs, rev, prop, this->m_pool));

			if (revision)
			{
				*revision = (long)rev;
			}
		}

		if (val && val->data)
		{
			result = val->data;
		}
	}
	finally
	{
		Marshal::FreeHGlobal(IntPtr((void*)crepo));
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< GetProperty {0}", gcnew String(result)));
#endif

	return result;
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetInfo(String^ repo)
{
	return GetInfo(repo, gcnew SvnRevOrTxn());
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetInfo(String^ repo, long revision)
{
	return GetInfo(repo, gcnew SvnRevOrTxn(revision));
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetInfo(String^ repo, String^ transaction)
{
	return GetInfo(repo, gcnew SvnRevOrTxn(transaction));
}

IronSvn::SvnLookInfo^ IronSvn::SvnClient::GetInfo(String^ repo, SvnRevOrTxn^ target)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> GetInfo {0}, {1}", repo, target));
#endif

	IronSvn::SvnLookInfo^ result = gcnew IronSvn::SvnLookInfo();
	IronSvn::SvnLookInfo^ i;
	i = GetAuthor(repo, target);
	if (i != nullptr)
	{
		result->Author = i->Author;
		result->Revision = i->Revision;
	}
	i = GetLog(repo, target);
	if (i != nullptr)
	{
		result->Log = i->Log;
		result->Revision = i->Revision;
	}
	i = GetDate(repo, target);
	if (i != nullptr)
	{
		result->Date = i->Date;
		result->Revision = i->Revision;
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< GetInfo {0}", result));
#endif

	return result;
}

List<IronSvn::SvnEntry^>^ IronSvn::SvnClient::Ls(String^ path)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> Ls {0}", path));
#endif

	List<IronSvn::SvnEntry^>^ result = gcnew List<IronSvn::SvnEntry^>();
	path = IronSvn::Svn::PreparePath(path);
	apr_hash_t* dirents;

	svn_opt_revision_t revision;
	revision.kind = svn_opt_revision_head;

	const char* cpath = (const char*)(Marshal::StringToHGlobalAnsi(path)).ToPointer();

	try
	{
		IronSvn::Svn::ThrowIfError(svn_client_ls(
			&dirents,
			cpath,
			&revision,
			true,
			this->m_ctx,
			this->m_pool
		));

		apr_array_header_t *ar = svn_sort__hash(dirents, svn_sort_compare_items_as_paths, this->m_pool);
		for (int i = 0; i < ar->nelts; i++)
		{
			IronSvn::SvnEntry^ entry = gcnew IronSvn::SvnEntry();
			result->Add(entry);
			const svn_sort__item_t *item;
            svn_dirent_t *dirent = NULL;

            item = &APR_ARRAY_IDX(ar, i, const svn_sort__item_t);
            dirent = (svn_dirent_t *)item->value;
			const char *key = (const char *)item->key;
			entry->Path = gcnew String(key);
			entry->Kind = (IronSvn::SvnNodeKind)((int)dirent->kind);
			entry->Size = (long)dirent->size;
			entry->HasProperties = dirent->has_props <= 0 ? FALSE : TRUE;
			entry->RevisionCreated = (long)dirent->created_rev;
			entry->Time = IronSvn::Svn::ConvertAprTimeToDateTime(dirent->time);
			if (dirent->last_author)
			{
				entry->LastAuthor = gcnew String(dirent->last_author);
			}
		}
	}
	finally
	{
		Marshal::FreeHGlobal(IntPtr((void*)cpath));
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< Ls {0}", result));
#endif

	return result;
}

IronSvn::SvnResult^ IronSvn::SvnClient::Update(String^ path)
{
	return this->Update(path, -1);
}

IronSvn::SvnResult^ IronSvn::SvnClient::Update(String^ path, long rev)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> Update {0}, {1}", path, rev));
#endif

	IronSvn::SvnResult^ result = gcnew IronSvn::SvnResult();
	svn_revnum_t resultrev;
	svn_opt_revision_t revision;

	if (rev < 0)
	{
		revision.kind = svn_opt_revision_unspecified;
	}
	else
	{
		revision.kind = svn_opt_revision_number;
		revision.value.number = rev;
	}

	path = IronSvn::Svn::PreparePath(path);

	const char* cpath = (const char*)(Marshal::StringToHGlobalAnsi(path)).ToPointer();
	gcroot<List<IronSvn::SvnAction^>^>* gclist = NULL;
	try
	{
		gclist = new gcroot<List<IronSvn::SvnAction^>^>(result->Actions);
		this->m_ctx->notify_baton2 = (void *)gclist;
		IronSvn::Svn::ThrowIfError(svn_client_update(
			&resultrev,
			cpath,
			&revision,
			true,
			this->m_ctx,
			this->m_pool
		));
	}
	finally
	{
		this->m_ctx->notify_baton2 = NULL;
		if (gclist)
		{
			delete gclist;
		}
		Marshal::FreeHGlobal(IntPtr((void*)cpath));
	}

	result->Result = resultrev;

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< Update {0}", result));
#endif

	return result;
}

IronSvn::SvnResult^ IronSvn::SvnClient::Commit(String^ path, String^ message)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> Commit {0}, {1}", path, message));
#endif

	IronSvn::SvnResult^ result = gcnew IronSvn::SvnResult();
	svn_commit_info_t* info = NULL;

	path = IronSvn::Svn::PreparePath(path);

	const char* cpath = (const char*)(Marshal::StringToHGlobalAnsi(path)).ToPointer();
	try
	{
		//array<unsigned char>^ bytes = System::Text::Encoding::UTF8->GetBytes(message);
		//pin_ptr<unsigned char> pinnedBytes(&bytes[0]);
		//const char* cmessage = reinterpret_cast<const char*>(static_cast<unsigned char *>(pinnedBytes));

		const char* cmessage = (const char*)(Marshal::StringToHGlobalAnsi(message)).ToPointer();
		try
		{
			apr_array_header_t* targets = IronSvn::Svn::GetStringAsAprArray(cpath, this->m_pool);

			const char* logMessage = apr_pstrdup(this->m_pool, cmessage);
			this->m_ctx->log_msg_baton2 = (void *)logMessage;
			gcroot<List<IronSvn::SvnAction^>^>* gclist = NULL;

			try
			{
				gclist = new gcroot<List<IronSvn::SvnAction^>^>(result->Actions);
				this->m_ctx->notify_baton2 = (void *)gclist;
				IronSvn::Svn::ThrowIfError(svn_client_commit3(
					&info,
					targets,
					true,
					false,
					this->m_ctx,
					this->m_pool
				));
			}
			finally
			{
				this->m_ctx->notify_baton2 = NULL;
				if (gclist)
				{
					delete gclist;
				}
				this->m_ctx->log_msg_baton2 = NULL;
			}

			result->Result = info ? info->revision : -1;
			return result;
		}
		finally
		{
			Marshal::FreeHGlobal(IntPtr((void*)cmessage));
		}
	}
	finally
	{
		Marshal::FreeHGlobal(IntPtr((void*)cpath));
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< Commit"));
#endif
}

svn_error_t* GetLogMessage(
	const char **log_msg,
	const char **tmp_file,
	const apr_array_header_t *commit_items,
	void *baton,
	apr_pool_t *pool
)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> GetLogMessage"));
#endif

	*log_msg = (const char *)baton;
	*tmp_file = NULL;

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< GetLogMessage"));
#endif

	return SVN_NO_ERROR;
}

void IronSvn::SvnClient::CreateRepository(String^ path)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> CreateRepository {0}", path));
#endif

	if (path == nullptr)
	{
		throw gcnew ArgumentNullException("path");
	}
	path = IronSvn::Svn::EnsureLocalPath(path);

	svn_repos_t* result = NULL;
	
	const char* cpath = (const char*)(Marshal::StringToHGlobalAnsi(path)).ToPointer();
	try
	{
		IronSvn::Svn::ThrowIfError(svn_repos_create(
			&result,
			cpath,
			NULL,
			NULL,
			NULL,
			NULL,
			this->m_pool
		));
	}
	finally
	{
		Marshal::FreeHGlobal(IntPtr((void*)cpath));
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< CreateRepository"));
#endif
}

void IronSvn::SvnClient::DeleteRepository(String^ path)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> DeleteRepository {0}", path));
#endif

	if (path == nullptr)
	{
		throw gcnew ArgumentNullException("path");
	}
	path = IronSvn::Svn::EnsureLocalPath(path);
	if (System::IO::Directory::Exists(path))
	{
		System::IO::Directory::Delete(path, true);
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< DeleteRepository"));
#endif
}

IronSvn::SvnResult^ IronSvn::SvnClient::Add(String^ path)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> Add {0}", path));
#endif

	IronSvn::SvnResult^ result = gcnew IronSvn::SvnResult();
	if (path == nullptr)
	{
		throw gcnew ArgumentNullException("path");
	}

	// If it's a local path and has a wildcard, expand the wildcard
	if (path->EndsWith("*"))
	{
		path = path->Substring(0, path->Length - 1);
		DirectoryInfo^ dir = gcnew DirectoryInfo(path);
		if (dir->Exists)
		{
			cli::array<FileSystemInfo^>^ fis = dir->GetFileSystemInfos();
			System::Collections::IEnumerator^ enumfis = fis->GetEnumerator();
			while (enumfis->MoveNext())
			{
				FileSystemInfo^ fi = (FileSystemInfo^)enumfis->Current;
				if (!this->HasTarget(fi->FullName) && fi->Name != ".svn")
				{
					this->AddInternal(result, fi->FullName);
				}
			}
		}
		else
		{
			throw gcnew IronSvn::SvnException("Path with wildcard not found: " + path);
		}
	}
	else
	{
		AddInternal(result, path);
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< Add {0}", result));
#endif

	return result;
}

void IronSvn::SvnClient::AddInternal(IronSvn::SvnResult^ result, String^ path)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> AddInternal {0}, {1}", result, path));
#endif

	if (path == nullptr)
	{
		throw gcnew ArgumentNullException("path");
	}

	path = IronSvn::Svn::PreparePath(path);

	const char* cpath = (const char*)(Marshal::StringToHGlobalAnsi(path)).ToPointer();
	gcroot<List<IronSvn::SvnAction^>^>* gclist = NULL;
	try
	{
		gclist = new gcroot<List<IronSvn::SvnAction^>^>(result->Actions);
		this->m_ctx->notify_baton2 = (void *)gclist;
		IronSvn::Svn::ThrowIfError(svn_client_add3(
			cpath,
			true,
			false,
			false,
			this->m_ctx,
			this->m_pool
		));
	}
	finally
	{
		this->m_ctx->notify_baton2 = NULL;
		if (gclist)
		{
			delete gclist;
		}
		Marshal::FreeHGlobal(IntPtr((void*)cpath));
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< AddInternal"));
#endif
}

IronSvn::SvnResult^ IronSvn::SvnClient::Delete(String^ path)
{
	return this->Delete(path, false);
}

IronSvn::SvnResult^ IronSvn::SvnClient::Delete(String^ path, bool force)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> Delete {0}, {1}", path, force));
#endif

	IronSvn::SvnResult^ result = gcnew IronSvn::SvnResult();
	if (path == nullptr)
	{
		throw gcnew ArgumentNullException("path");
	}
	svn_commit_info_t* resultrev = NULL;
	path = IronSvn::Svn::PreparePath(path);

	const char* cpath = (const char*)(Marshal::StringToHGlobalAnsi(path)).ToPointer();
	gcroot<List<IronSvn::SvnAction^>^>* gclist = NULL;
	try
	{
		apr_array_header_t* targets = IronSvn::Svn::GetStringAsAprArray(cpath, this->m_pool);
		gclist = new gcroot<List<IronSvn::SvnAction^>^>(result->Actions);
		this->m_ctx->notify_baton2 = (void *)gclist;

		IronSvn::Svn::ThrowIfError(svn_client_delete2(
			&resultrev,
			targets,
			force,
			this->m_ctx,
			this->m_pool
		));

		result->Result = resultrev ? (SVN_IS_VALID_REVNUM(resultrev->revision) ? resultrev->revision : -1) : -1;
		return result;
	}
	finally
	{
		this->m_ctx->notify_baton2 = NULL;
		if (gclist)
		{
			delete gclist;
		}
		Marshal::FreeHGlobal(IntPtr((void*)cpath));
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< Delete"));
#endif
}

IronSvn::SvnResult^ IronSvn::SvnClient::MakeDirectory(String^ dir)
{
	return this->MakeDirectory(dir, "");
}

IronSvn::SvnResult^ IronSvn::SvnClient::MakeDirectory(String^ dir, String^ message)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> MakeDirectory {0}, {1}", dir, message));
#endif

	IronSvn::SvnResult^ result = gcnew IronSvn::SvnResult();
	if (dir == nullptr)
	{
		throw gcnew ArgumentNullException("dir");
	}
	dir = IronSvn::Svn::PreparePath(dir);
	svn_commit_info_t* resultrev = NULL;

	const char* cdir = (const char*)(Marshal::StringToHGlobalAnsi(dir)).ToPointer();
	try
	{
		apr_array_header_t* targets = IronSvn::Svn::GetStringAsAprArray(cdir, this->m_pool);

		const char* cmessage = (const char*)(Marshal::StringToHGlobalAnsi(message)).ToPointer();
		try
		{
			const char* logMessage = apr_pstrdup(this->m_pool, cmessage);
			this->m_ctx->log_msg_baton2 = (void *)logMessage;
			gcroot<List<IronSvn::SvnAction^>^>* gclist = NULL;

			try
			{
				gclist = new gcroot<List<IronSvn::SvnAction^>^>(result->Actions);
				this->m_ctx->notify_baton2 = (void *)gclist;
				IronSvn::Svn::ThrowIfError(svn_client_mkdir2(
					&resultrev,
					targets,
					this->m_ctx,
					this->m_pool
				));

				result->Result = resultrev ? (SVN_IS_VALID_REVNUM(resultrev->revision) ? resultrev->revision : -1) : -1;
				return result;
			}
			finally
			{
				this->m_ctx->notify_baton2 = NULL;
				if (gclist)
				{
					delete gclist;
				}
				this->m_ctx->log_msg_baton2 = NULL;
			}
		}
		finally
		{
			Marshal::FreeHGlobal(IntPtr((void*)cmessage));
		}
	}
	finally
	{
		Marshal::FreeHGlobal(IntPtr((void*)cdir));
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< MakeDirectory"));
#endif
}

IronSvn::SvnResult^ IronSvn::SvnClient::Move(String^ src, String^ dest)
{
	return this->Move(src, dest, "");
}

IronSvn::SvnResult^ IronSvn::SvnClient::Move(String^ src, String^ dest, String^ message)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> Move {0}, {1}, {2}", src, dest, message));
#endif

	IronSvn::SvnResult^ result = gcnew IronSvn::SvnResult();
	if (src == nullptr)
	{
		throw gcnew ArgumentNullException("src");
	}
	if (dest == nullptr)
	{
		throw gcnew ArgumentNullException("dest");
	}
	svn_commit_info_t* resultrev = NULL;
	src = IronSvn::Svn::PreparePath(src);
	dest = IronSvn::Svn::PreparePath(dest);

	const char* csrc = (const char*)(Marshal::StringToHGlobalAnsi(src)).ToPointer();
	try
	{
		const char* cdest = (const char*)(Marshal::StringToHGlobalAnsi(dest)).ToPointer();
		gcroot<List<IronSvn::SvnAction^>^>* gclist = NULL;
		try
		{
			gclist = new gcroot<List<IronSvn::SvnAction^>^>(result->Actions);
			this->m_ctx->notify_baton2 = (void *)gclist;
			IronSvn::Svn::ThrowIfError(svn_client_move4(
				&resultrev,
				csrc,
				cdest,
				false,
				this->m_ctx,
				this->m_pool
			));

			result->Result = resultrev ? (SVN_IS_VALID_REVNUM(resultrev->revision) ? resultrev->revision : -1) : -1;
			return result;
		}
		finally
		{
			this->m_ctx->notify_baton2 = NULL;
			if (gclist)
			{
				delete gclist;
			}
			Marshal::FreeHGlobal(IntPtr((void*)cdest));
		}
	}
	finally
	{
		Marshal::FreeHGlobal(IntPtr((void*)csrc));
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< Move"));
#endif
}

IronSvn::SvnInfo^ IronSvn::SvnClient::Info(String^ pathOrUrl)
{
	List<IronSvn::SvnInfo^>^ result = this->Info(pathOrUrl, false);
	if (result->Count > 0)
	{
		return result[0];
	}
	return nullptr;
}

System::Collections::Generic::List<IronSvn::SvnInfo^>^ IronSvn::SvnClient::Info(String^ pathOrUrl, bool recursive)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> Info {0}, {1}", pathOrUrl, recursive));
#endif

	System::Collections::Generic::List<IronSvn::SvnInfo^>^ result = gcnew System::Collections::Generic::List<IronSvn::SvnInfo^>();
	svn_opt_revision_t peg_revision;
	svn_opt_revision_t revision;

	peg_revision.kind = svn_opt_revision_unspecified;
	revision.kind = svn_opt_revision_unspecified;

	pathOrUrl = IronSvn::Svn::PreparePath(pathOrUrl);
	const char* cpathOrUrl = (const char*)(Marshal::StringToHGlobalAnsi(pathOrUrl)).ToPointer();
	gcroot<System::Collections::Generic::List<IronSvn::SvnInfo^>^>* gclist = NULL;
	try
	{
		gclist = new gcroot<System::Collections::Generic::List<IronSvn::SvnInfo^>^>(result);
		IronSvn::Svn::ThrowIfError(svn_client_info(
			cpathOrUrl,
			&peg_revision,
			&revision,
			(svn_info_receiver_t)&InfoReceiver,
			(void *)gclist,
			recursive,
			this->m_ctx,
			this->m_pool
		));
	}
	finally
	{
		if (gclist != NULL)
		{
			delete gclist;
		}
		Marshal::FreeHGlobal(IntPtr((void*)cpathOrUrl));
	}

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< Info {0}", result));
#endif

	return result;
}

svn_error_t* InfoReceiver(
	void *baton,
	const char *path,
	const svn_info_t *info,
	apr_pool_t *pool
)
{
#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("> InfoReceiver"));
#endif

	gcroot<System::Collections::Generic::List<IronSvn::SvnInfo^>^>* list = (gcroot<System::Collections::Generic::List<IronSvn::SvnInfo^>^>*)baton;
	IronSvn::SvnInfo^ i = gcnew IronSvn::SvnInfo();
	i->Path = gcnew String(path);
	i->Url = gcnew String(info->URL);
	i->RepositoryRootUrl = gcnew String(info->repos_root_URL);
	i->RepositoryUuid = gcnew String(info->repos_UUID);
	i->Revision = info->rev;
	i->NodeKind = (IronSvn::SvnNodeKind)((int)info->kind);

	switch (info->schedule)
	{
	case svn_wc_schedule_normal:
		i->Schedule = IronSvn::SvnSchedule::Normal;
		break;
	case svn_wc_schedule_add:
		i->Schedule = IronSvn::SvnSchedule::Add;
		break;
	case svn_wc_schedule_delete:
		i->Schedule = IronSvn::SvnSchedule::Delete;
		break;
	case svn_wc_schedule_replace:
		i->Schedule = IronSvn::SvnSchedule::Replace;
		break;
	}

	i->LastChangedRevision = info->last_changed_rev;
	i->LastChangedDate = IronSvn::Svn::ConvertAprTimeToDateTime(info->last_changed_date);

	(*list)->Add(i);

#ifdef LOGS
	if (IronSvn::Svn::IsLogEnabled) Debug::WriteLine(String::Format("< InfoReceiver {0}", i));
#endif

	return SVN_NO_ERROR;
}

String^ IronSvn::SvnClient::UserName::get()
{
	return this->m_userName;
}

void IronSvn::SvnClient::UserName::set(String^ userName)
{
	this->m_userName = userName;
	
	if (this->m_userChars)
	{
		Marshal::FreeHGlobal(IntPtr((void*)this->m_userChars));
		this->m_userChars = NULL;
	}

	if (this->m_ctx && this->m_ctx->auth_baton && userName != nullptr)
	{
		this->m_userChars = (const char*)(Marshal::StringToHGlobalAnsi(this->m_userName)).ToPointer();
		svn_auth_set_parameter(
			this->m_ctx->auth_baton,
			SVN_AUTH_PARAM_DEFAULT_USERNAME,
			this->m_userChars
		);
	}
}

const char* IronSvn::SvnClient::UserNameChars::get()
{
	return this->m_userChars;
}

String^ IronSvn::SvnClient::Password::get()
{
	return this->m_password;
}

void IronSvn::SvnClient::Password::set(String^ password)
{
	this->m_password = password;

	if (this->m_passwordChars)
	{
		Marshal::FreeHGlobal(IntPtr((void*)this->m_passwordChars));
		this->m_passwordChars = NULL;
	}

	if (this->m_ctx && this->m_ctx->auth_baton && password != nullptr)
	{
		this->m_passwordChars = (const char*)(Marshal::StringToHGlobalAnsi(this->m_password)).ToPointer();
		svn_auth_set_parameter(
			this->m_ctx->auth_baton,
			SVN_AUTH_PARAM_DEFAULT_PASSWORD,
			this->m_passwordChars
		);
	}
}

const char* IronSvn::SvnClient::PasswordChars::get()
{
	return this->m_passwordChars;
}
